package com.bolt.report.business;


import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.convert.Convert;
import com.bolt.common.utils.JacksonUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.convention.exception.ServiceException;
import com.bolt.core.view.components.*;
import com.bolt.core.view.config.ConfigUtils;
import com.bolt.report.business.modules.dataset.dto.ReportDataSetDTO;
import com.bolt.report.business.modules.datasource.dto.ReportDataSourceDTO;
import com.bolt.report.business.modules.field.dto.ReportDbFieldDTO;
import com.bolt.report.business.modules.param.dto.ReportDbParamDTO;
import com.bolt.report.business.modules.report.dto.ReportDTO;
import com.bolt.report.business.modules.report.service.ReportService;
import com.bolt.report.definition.*;
import com.bolt.report.engine.AbstractReportManager;
import com.bolt.report.engine.ReportConstants;
import com.bolt.report.engine.fill.HyperlinkData;
import com.bolt.report.exception.ReportBizExceptionEnum;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.node.ArrayNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Service("dbReportDefManager")
@Transactional(readOnly = true, rollbackFor = Exception.class)
@RequiredArgsConstructor
@Slf4j
public class ReportDBDefManager extends AbstractReportManager {

    private final ReportService reportService;


    @Override
    public ReportDefinition parseReportDefinition(String report) {
        try {
            ReportDTO reportDto = reportService.load(report);
            String context = reportDto.getContextDef();
            JsonNode jsonObject = JacksonUtil.getObjectMapper().readTree(context);
            ReportDefinition definition = new ReportDefinition();
            definition.setReportFullName(reportDto.getName());
            definition.setDesc(reportDto.getNote());
            definition.setId(reportDto.getId());
            parseDefinition(definition, jsonObject);
            if (jsonObject.has("paper")) {
                PaperDefinition paper = parsePaper(jsonObject.get("paper"));
                definition.setPaper(paper);
            }
            List<DataSetDefinition> dataSetDefinitions = parseDataSet(reportDto);
            definition.setDataSetDefinitions(dataSetDefinitions);
            List<Integer> columnDef = parseColumns(jsonObject);
            definition.setColumnDefinition(columnDef);
            parseElement(jsonObject, definition);
            if (jsonObject.has("queryFormData")) {
                Form form = createQueryForm(jsonObject.get("queryFormData"), definition);
                form.setId("query_form_" + definition.getId());
                definition.setQueryForm(form);
            }
            return definition;
        } catch (Exception ex) {
            log.error("parse Report definition error", ex);
            throw new ServiceException(ex);
        }
    }


    private void parseDefinition(ReportDefinition definition, JsonNode jsonObject) {
        if (jsonObject.has("isPagination")) {
            definition.setIgnorePagination(jsonObject.get("isPagination").asBoolean());
        }
        if (jsonObject.has("isLoopPrintTitle")) {
            definition.setLoopPrintTitle(jsonObject.get("isLoopPrintTitle").asBoolean());
        }
        if (jsonObject.has("isLoopPrintHead")) {
            definition.setLoopPrintHead(jsonObject.get("isLoopPrintHead").asBoolean());
        }
    }

    private PaperDefinition parsePaper(JsonNode paperObj) {
        PaperDefinition paper = new PaperDefinition();

        paper.setLeftMargin(paperObj.get("leftMargin").asInt(10));
        paper.setRightMargin(paperObj.get("rightMargin").asInt(10));
        paper.setTopMargin(paperObj.get("topMargin").asInt(10));
        paper.setBottomMargin(paperObj.get("bottomMargin").asInt(10));
        PaperType paperType = PaperType.valueOf(paperObj.get("paperType").asText("A4"));
        paper.setPrintOrderValue(PrintOrder.valueOf(paperObj.get("printOrderValue").asText("VERTICAL")));
        if (paperType == PaperType.CUSTOM) {
            paper.setWidth(paperObj.get("width").asInt());
            paper.setHeight(paperObj.get("height").asInt());
        } else {
            PaperSize paperSize = paperType.getPaperSize();
            paper.setWidth(paperSize.getWidth());
            paper.setHeight(paperSize.getHeight());
        }
        paper.setPaperType(paperType);

        return paper;
    }

    public List<DataSetDefinition> parseDataSet(ReportDTO reportDto) {
        List<DataSetDefinition> dataSetDefinitions = new ArrayList<>();
        Set<ReportDataSetDTO> dataSetDtos = reportDto.getDataSets();
        if (IteratorUtil.isNotEmpty(dataSetDtos)) {
            for (ReportDataSetDTO dto : dataSetDtos) {
                DataSetDefinition dataSetDefinition = createDataSetDefinition(dto);
                dataSetDefinitions.add(dataSetDefinition);
            }
        }
        return dataSetDefinitions;
    }

    public DataSetDefinition createDataSetDefinition(ReportDataSetDTO dto) {
        DataSetDefinition dataSetDefinition = new DataSetDefinition();
        dataSetDefinition.setKey(dto.getDbCode());
        dataSetDefinition.setName(dto.getDbChName());
        dataSetDefinition.setQuerySql(dto.getDbDynSql());
        dataSetDefinition.setType(DataSetType.toType(dto.getDbType()));
        dataSetDefinition.setPage(dto.isPage());
        dataSetDefinition.setList(dto.isList());
        dataSetDefinition.setSpringKey(dto.getSpringKey());
        dataSetDefinition.setApiUrl(dto.getApiUrl());
        dataSetDefinition.setApiConvert(dto.getApiConvert());
        dataSetDefinition.setApiMethod(dto.getApiMethod());
        dataSetDefinition.setApiRequestType(dto.getApiRequestType());
        dataSetDefinition.setJsonData(dto.getJsonData());

        ReportDataSourceDTO dataSourceDto = dto.getReportDataSource();
        if (dataSourceDto != null) {
            JdbcDataSourceDefinition jdbcDefinition = new JdbcDataSourceDefinition();
            jdbcDefinition.setCode(dataSourceDto.getCode());
            jdbcDefinition.setName(dataSourceDto.getName());
            jdbcDefinition.setDbUrl(dataSourceDto.getDbUrl());
            jdbcDefinition.setDbDriver(dataSourceDto.getDbDriver());
            jdbcDefinition.setDbType(dataSourceDto.getDbType());
            jdbcDefinition.setDbUsername(dataSourceDto.getDbUsername());
            jdbcDefinition.setDbPassword(dataSourceDto.getDbPassword());
            dataSetDefinition.setJdbcConnectionDef(jdbcDefinition);
        }
        List<FieldDefinition> fields = new ArrayList<>();
        for (ReportDbFieldDTO field : dto.getFields()) {
            fields.add(new FieldDefinition(field.getFieldName(), field.getOrderType(), field.getFieldExpr()));
        }
        List<ParameterDefinition> parameters = new ArrayList<>();
        for (ReportDbParamDTO data : dto.getParams()) {
            parameters.add(new ParameterDefinition(data.getParamName(), data.getParamValue(), ParameterType.valueOf(data.getParamType())));
        }
        dataSetDefinition.setFields(fields);
        dataSetDefinition.setParameters(parameters);
        return dataSetDefinition;
    }

    public List<Integer> parseColumns(JsonNode jsonNode) {
        ArrayNode columns = (ArrayNode) jsonNode.get("columns");
        List<Integer> columnDef = new ArrayList<>(columns.size());
        for (JsonNode column : columns) {
            columnDef.add(column.get("width").asInt(100));
        }
        return columnDef;
    }


    private void parseElement(JsonNode jsonNode, ReportDefinition definition) {
        ArrayNode rows = (ArrayNode) jsonNode.get("rows");
        for (int i = 0; i < rows.size(); i++) {
            JsonNode row = rows.get(i);
            RowDefinition rowDefinition = new RowDefinition(i, row.get("height").asInt(25));
            rowDefinition.setCellDefinitions(createCellDefinition(row));
            if (row.has("band")) {
                if ("title".equals(row.get("band").asText())) {
                    definition.getTitle().add(rowDefinition);
                } else if ("header".equals(row.get("band").asText())) {
                    definition.getHeads().add(rowDefinition);
                } else if ("summary".equals(row.get("band").asText())) {
                    definition.getSummary().add(rowDefinition);
                } else if ("group_summary".equals(row.get("band").asText())) {
                    definition.getGroupSummary().add(rowDefinition);
                }
            } else {
                definition.getElements().add(rowDefinition);
            }
        }
        if ((definition.haveHead() || definition.haveTitle()) && definition.haveElements()) {
            List<RowDefinition> rowDefinitions = definition.getElements();
            for (int i = 0; i < rowDefinitions.size(); i++) {
                RowDefinition rowDefinition = rowDefinitions.get(i);
                for (CellDefinition cellDefinition : rowDefinition.getCellDefinitions()) {
                    cellDefinition.setY(i);
                }
            }
        }
    }

    private List<CellDefinition> createCellDefinition(JsonNode row) {
        ArrayNode columns = (ArrayNode) row.get("cells");

        List<CellDefinition> rowCellDefinition = new ArrayList<>(columns.size());
        for (JsonNode column : columns) {
            JsonNode value = column.findValue("value");
            CellDefinition cellDefinition = new CellDefinition();
            if (column.has("cellStyle")) {
                JsonNode cellStyle = column.get("cellStyle");
                cellDefinition.setStyle(JacksonUtil.parseObject(cellStyle.toString(), StyleDefinition.class));
            }
            if (value.has("type")) {
                ElementType elementType = ElementType.valueOf(value.get("type").asText("TEXT").toUpperCase());
                cellDefinition.setElementType(elementType);
            } else {
                cellDefinition.setElementType(ElementType.TEXT);
            }
            if (column.has("x")) {
                cellDefinition.setX(column.get("x").asInt());
            }
            if (column.has("y")) {
                cellDefinition.setY(column.get("y").asInt());
            }
            if (column.has("colSpan")) {
                cellDefinition.setColSpan(column.get("colSpan").asInt());
            }
            if (column.has("rowSpan")) {
                cellDefinition.setRowSpan(column.get("rowSpan").asInt());
            }

            if (column.has("width") && !value.has("width")) {
                cellDefinition.setCellWidth(column.get("width").asInt(100));
                cellDefinition.setWidth(column.get("width").asInt(100));
            }

            if (column.has("height") && !value.has("height")) {
                cellDefinition.setCellHeight(column.get("height").asInt(100));
            }

            if (value.has("fieldName")) {
                cellDefinition.setName(value.get("fieldName").asText());
            }
            if (value.has("dbCode")) {
                cellDefinition.setKey(value.get("dbCode").asText());
            }
            if (value.has("expr")) {
                parseExpr(cellDefinition, value.get("expr").asText());
            }

            if (cellDefinition.getElementType() == ElementType.CHART) {
                cellDefinition.setJsonData(value);
            }
            if (cellDefinition.getElementType() == ElementType.CROSSTAB) {
                cellDefinition.setLabels(value.get("labels").asText());
                cellDefinition.setImg(value.get("img").asText());
                cellDefinition.setWidth(value.get("width").asInt());
                cellDefinition.setHeight(value.get("height").asInt());
            }
            if (cellDefinition.getElementType() == ElementType.IMG) {
                cellDefinition.setImg(value.get("img").asText());
                cellDefinition.setWidth(value.get("width").asInt());
                cellDefinition.setHeight(value.get("height").asInt());
            }

            if (cellDefinition.getElementType() == ElementType.BARCODE || cellDefinition.getElementType() == ElementType.QRCODE) {
                cellDefinition.setImg(value.get("img").asText());
                cellDefinition.setWidth(value.get("width").asInt());
                cellDefinition.setHeight(value.get("height").asInt());
                cellDefinition.setJsonData(value.get("option"));
            }

            if (value.has("linkId") && !value.get("linkId").isEmpty()) {
                try {
                    ObjectReader reader = JacksonUtil.getObjectMapper()
                            .readerFor(new TypeReference<List<String>>() {
                            });
                    List<String> linkIds = reader.readValue((ArrayNode) value.get("linkId"));
                    List<HyperlinkDefinition> definitions = reportService.loadHyperlinkConfig(linkIds);
                    cellDefinition.setHyperlinks(new HyperlinkData(definitions));
                } catch (Exception ex) {
                    log.error(ex.getMessage(), ex);
                }
            }
            if (value.has("isGroup") && value.get("isGroup").asBoolean()) {
                DataExpand dataExpand = DataExpand.DOWN;
                if (column.has("expand")) {
                    String expand = column.get("expand").asText();
                    dataExpand = DataExpand.valueOf(expand.toUpperCase());
                }
                SortOrder sortOrder = SortOrder.ASC;
                if (column.has("sort")) {
                    String sort = column.get("sort").asText();
                    if (!"none".equals(sort)) {
                        sortOrder = SortOrder.valueOf(sort.toUpperCase());
                    }
                }
                cellDefinition.setGroup(true);
                cellDefinition.setDataExpand(dataExpand);
                cellDefinition.setSortOrder(sortOrder);
            }
            rowCellDefinition.add(cellDefinition);
        }

        return rowCellDefinition;
    }

    private void parseExpr(CellDefinition definition, String expr) {
        Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}");
        Matcher matcher = pattern.matcher(expr);
        if (!matcher.find()) {
            definition.setName(expr);
            return;
        }
        Set<String> keys = new HashSet<>();
        int dynamicCount = 0;
        do {
            String dynamicExpr = matcher.group(1);
            int startIndex = StrUtil.matchLastIndex(dynamicExpr, "[(]");
            int endIndex = StrUtil.matchIndex(dynamicExpr, "[)]");
            if (startIndex > 0 && endIndex > 0) {
                dynamicExpr = StrUtil.sub(dynamicExpr, startIndex + 1, endIndex);
            }
            if (dynamicExpr.indexOf(StrUtil.DOT) > -1) {
                String key = StrUtil.subBefore(dynamicExpr, StrUtil.DOT, false);
                definition.setKey(key);
                keys.add(key);
            } else {
                definition.setExpression(dynamicExpr);
            }
            dynamicCount++;
        } while (matcher.find());
        if (keys.size() > 1) {
            throw new ServiceException(ReportBizExceptionEnum.EXPRESSION_KEY_ERROR);
        }
        if (dynamicCount > 1) {
            definition.setName(null);
        }
        definition.setExpression(expr);
    }

    public Form createQueryForm(JsonNode form, ReportDefinition definition) {
        ArrayNode components = (ArrayNode) form.get("list");
        Form from = new Form();
        for (JsonNode component : components) {
            String componentType = component.get("type").asText();
            if ("table".equals(componentType)) {
                parseQueryForm(component, from, definition);
            } else {
                parseQueryFormItem(components, from, definition);
                break;
            }
        }
        return from;
    }

    private void parseQueryForm(JsonNode table, Form form, ReportDefinition definition) {
        ArrayNode trs = (ArrayNode) table.get("trs");
        for (JsonNode tr : trs) {
            form.newRow();
            ArrayNode tds = (ArrayNode) tr.get("tds");
            for (JsonNode td : tds) {
                if (td.has("list")) {
                    parseQueryFormItem((ArrayNode) td.get("list"), form, definition);
                }
            }
        }
    }

    private void parseQueryFormItem(ArrayNode formItems, Form from, ReportDefinition definition) {
        for (JsonNode itemValue : formItems) {
            String type = itemValue.get("type").asText();
            FormItem formItem;
            switch (type) {
                case "select":
                    formItem = new SelectBase();
                    setSelectOptions(formItem, itemValue.get("options"));
                    break;
                case "radio":
                    formItem = createCombinationFormItem(itemValue);
                    break;
                case "checkbox":
                    formItem = createCombinationFormItem(itemValue);
                    break;

                default:
                    formItem = new FormItem();

            }
            setFormItemValue(formItem, itemValue, definition);
            parseValidatorRoles(itemValue, from, formItem);
            from.appendFormItems(formItem);

        }
    }

    private CombinationFormItem createCombinationFormItem(JsonNode item) {
        CombinationFormItem combinationFormItem = new CombinationFormItem();
        JsonNode optionsNode = item.get("options");
        if (item.has("options")) {
            ArrayNode options = (ArrayNode) optionsNode.get("options");
            for (JsonNode optionsValue : options) {
                FormItem formItem = new FormItem();
                formItem.setType(item.get("type").asText());
                formItem.setLabel(optionsValue.get("label").asText());
                formItem.addParameter("value", optionsValue.get("value").asText());
                formItem.setId(item.get("key").asText());
                formItem.setName(item.get("name").asText());
                combinationFormItem.addCombinationFormItem(formItem);
            }
        }
        return combinationFormItem;
    }

    private void setFormItemValue(FormItem formItem, JsonNode item, ReportDefinition definition) {
        formItem.setType(item.get("type").asText());
        formItem.setId(item.get("key").asText());
        formItem.setLabel(item.get("label").asText());
        formItem.setName(item.get("name").asText());
        JsonNode options = item.get("options");
        for (Iterator<String> it = options.fieldNames(); it.hasNext(); ) {
            String name = it.next();
            String value = StrUtil.trimToNull(options.get(name).asText());
            if (StrUtil.equalsIgnoreCase(value, "true") || StrUtil.equalsIgnoreCase(value, "false")) {
                formItem.addParameter(name, Convert.toBool(value));
            } else {
                formItem.addParameter(name, value);
            }
        }
        if (options.has("customOptions")) {
            String customOptions = options.get("customOptions").asText();
            if (StrUtil.isNotBlank(customOptions)) {
                formItem.addAllParameters(ConfigUtils.parserConfigValue(customOptions));
            }
        }
        setBindDataModel(formItem, item, definition);
        setBindEventModel(formItem, item, definition);
    }

    private void setBindDataModel(FormItem formItem, JsonNode item, ReportDefinition definition) {
        if (item.has("bindData") && !item.get("bindData").isEmpty()) {
            BindDataModel bindDataModel = JacksonUtil.parseObject(item.get("bindData").toString(), BindDataModel.class);
            formItem.setBindDataModel(bindDataModel);
            if (bindDataModel.isTreeData()) {
                formItem.setType("selectTree");
                formItem.addParameter(ReportConstants.FORM_SELECT_DATA_URL, StrUtil.format("/report/data/getTree?reportId={}&formItemId={}{}", definition.getId(), formItem.getId(), bindDataModel.getQueryParam()));
            }

            if (bindDataModel.isInitData() && !bindDataModel.isTreeData()) {
                formItem.addParameter("loader", ReportConstants.FORM_SELECT_ITEM_BIND_DATA_NAME);
            }


        }
    }

    private void setBindEventModel(FormItem formItem, JsonNode item, ReportDefinition definition) {
        if (item.has("event") && !item.get("event").isEmpty()) {
            ArrayNode events = (ArrayNode) item.get("event");
            List eventList = new ArrayList();
            for (JsonNode event : events) {
                BindEventModel bindEventModel = JacksonUtil.parseObject(event.toString(), BindEventModel.class);
                if ("ldSelect".equals(bindEventModel.getType())) {
                    formItem.addParameter(ReportConstants.FORM_SELECT_REF_ID, bindEventModel.getRefId());
                    String url = StrUtil.format("/report/data/getData?reportId={}&refId={}{}", definition.getId(), bindEventModel.getRefId(), bindEventModel.getQueryParam());
                    formItem.addParameter(ReportConstants.FORM_SELECT_REF_DATA_URL, url);
                }
                formItem.addParameter(bindEventModel.getType(),bindEventModel.getType());
                eventList.add(bindEventModel);
            }
            formItem.setBindEventModels(eventList);
        }
    }


    private void setSelectOptions(FormItem formItem, JsonNode options) {
        if (options.has("options") && options.get("dynamic").asInt() == 0) {
            ArrayNode itemValues = (ArrayNode) options.get("options");
            Map<String, String> data = new LinkedHashMap<>();
            for (JsonNode itemValue : itemValues) {
                String value = itemValue.get("value").asText();
                String label = itemValue.get("label").asText();
                data.put(value, label);
            }
            formItem.addParameter("itemValue", data);
        }
    }

    private void parseValidatorRoles(JsonNode item, Form from, FormItem formItem) {
        if (item.has("rules")) {
            ArrayNode rules = (ArrayNode) item.get("rules");
            for (JsonNode rule : rules) {
                List<String> typeValue = new ArrayList<>();
                if (rule.get("type").isArray()) {
                    ArrayNode multiple = (ArrayNode) rule.get("type");


                    typeValue = StreamSupport.stream(
                            Spliterators.spliteratorUnknownSize(multiple.iterator(), Spliterator.ORDERED), false)
                            .map(x -> x.asText())
                            .collect(Collectors.toList());

                } else {
                    typeValue.add(rule.get("type").asText());
                }

                String expr = rule.get("expr").asText();
                if (StrUtil.isNotBlank(expr)) {
                    typeValue.addAll(StrUtil.split(expr, '|', 10));
                }
                List<String> messageValue = new ArrayList<>();
                String message = rule.get("message").asText();
                if (StrUtil.isNotBlank(message)) {
                    messageValue.addAll(StrUtil.split(message, '|', 10));
                }
                if (IteratorUtil.isNotEmpty(typeValue)) {
                    ValidatorRule validatorRule = new ValidatorRule();
                    validatorRule.setId(formItem.getId());
                    typeValue.forEach(k -> {
                        if (StrUtil.isNotBlank(k)) validatorRule.handleValidatorValue(k);
                    });
                    validatorRule.setMessage(messageValue);
                    validatorRule.setParent(from);
                    from.addValidators(validatorRule);
                }

            }
        }


    }


    @Override
    public void removeCache(String report) {
        this.definitionLookup.remove(report);
    }
}